#define LOG_TAG "ui.font"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

#include "font.h"
#include "esp_log.h"

#if !defined(FONT_ENABLE_BASE_i18n)
#include "base_font.h"
#endif

#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__)
#define uintp_t uint64_t
#elif defined(_WIN32) || defined(__x86_32__) || defined(__ppc32__)
#endif

#define log_e(...) ESP_LOGE(TAG, __VA_ARGS__)
#define log_i(...) ESP_LOGI(TAG, __VA_ARGS__)
#define log_w(...) ESP_LOGW(TAG, __VA_ARGS__)

static const char *TAG = "font";
#define i18n_null lv_font_montserrat_14

#if defined(CONFIG_IDF_TARGET) && (defined(FONT_ENABLE_BASE_i18n) || defined(FONT_ENABLE_i18n))
#include "esp_partition.h"
static esp_partition_mmap_handle_t handle = 0;
static uint32_t i18n_font_buf_size = 0;
static uint8_t *i18n_font_buf = 0;
#endif

typedef struct base_font_data
{
    const char *name;
    void *font;
    lv_coord_t line_height;
} base_font_data_t;

lv_font_t *base_font8 = &i18n_null;
lv_font_t *base_font12 = &i18n_null;
lv_font_t *base_font14 = &i18n_null;
lv_font_t *base_font16 = &i18n_null;
lv_font_t *base_font18 = &i18n_null;
lv_font_t *base_font20 = &i18n_null;
lv_font_t *base_font22 = &i18n_null;
lv_font_t *base_font24 = &i18n_null;
lv_font_t *base_font28 = &i18n_null;
lv_font_t *base_font36 = &i18n_null;
lv_font_t *base_font38 = &i18n_null;
lv_font_t *base_font42 = &i18n_null;
lv_font_t *base_font48 = &i18n_null;

static const base_font_data_t base_font[] = {
    {.name = "base_font8", .font = &base_font8, .line_height = 14},
    {.name = "base_font12", .font = &base_font12, .line_height = 18},
    {.name = "base_font14", .font = &base_font14, .line_height = 20},
    {.name = "base_font16", .font = &base_font16, .line_height = 23},
    {.name = "base_font18", .font = &base_font18, .line_height = 25},
    {.name = "base_font20", .font = &base_font20, .line_height = 28},
    {.name = "base_font22", .font = &base_font22, .line_height = 31},
    {.name = "base_font24", .font = &base_font24, .line_height = 34},
    {.name = "base_font28", .font = &base_font28, .line_height = 40},
    {.name = "base_font36", .font = &base_font36, .line_height = 60},
    {.name = "base_font38", .font = &base_font38, .line_height = 65},
    {.name = "base_font42", .font = &base_font42, .line_height = 72},
    {.name = "base_font48", .font = &base_font48, .line_height = 82},
};
#ifdef FONT_ENABLE_i18n
typedef struct i18n_font_data
{
    const char *name;
    void *font_p;
    lv_font_t font;
    lv_font_t **unload_font;
    const lv_coord_t line_height;
} i18n_font_data_t;

i18n_font_data_t i18n_font_data[] = {
    {
        .name = "i18n_font8",
        .font_p = NULL,
        .font = {.get_glyph_bitmap = __user_font_get_bitmap, .get_glyph_dsc = __user_font_get_glyph_dsc, .line_height = 18, .base_line = 0},
        .unload_font = &base_font8,
        .line_height = 18,
    },
    {
        .name = "i18n_font12",
        .font_p = NULL,
        .font = {.get_glyph_bitmap = __user_font_get_bitmap, .get_glyph_dsc = __user_font_get_glyph_dsc, .line_height = 24, .base_line = 0},
        .unload_font = &base_font16,
        .line_height = 22,
    },
    {
        .name = "i18n_font18",
        .font_p = NULL,
        .font = {.get_glyph_bitmap = __user_font_get_bitmap, .get_glyph_dsc = __user_font_get_glyph_dsc, .line_height = 30, .base_line = 0},
        .unload_font = &base_font16,
        .line_height = 28,
    },
    {
        .name = "i18n_font20",
        .font_p = NULL,
        .font = {.get_glyph_bitmap = __user_font_get_bitmap, .get_glyph_dsc = __user_font_get_glyph_dsc, .line_height = 32, .base_line = 0},
        .unload_font = &base_font22,
        .line_height = 30,
    },
    {
        .name = "i18n_font22",
        .font_p = NULL,
        .font = {.get_glyph_bitmap = __user_font_get_bitmap, .get_glyph_dsc = __user_font_get_glyph_dsc, .line_height = 36, .base_line = 0},
        .unload_font = &base_font24,
        .line_height = 34,
    },
};

lv_font_t *i18n_font8 = &i18n_font_data[0].font;
lv_font_t *i18n_font12 = &i18n_font_data[1].font;
lv_font_t *i18n_font18 = &i18n_font_data[2].font;
lv_font_t *i18n_font20 = &i18n_font_data[3].font;
lv_font_t *i18n_font22 = &i18n_font_data[4].font;

bool i18n_font_load(uint8_t *data, uint32_t size)
{
    lv_font_t *font;
    for (uint8_t i = 0; i < sizeof(i18n_font_data) / sizeof(i18n_font_data[0]); i++)
    {
        font = lv_tiny_ttf_create_data(data, size, i18n_font_data[i].line_height);
        if (font)
        {
            font->fallback = &i18n_null;
            log_i("Create %s", i18n_font_data[i].name);
            memcpy(&i18n_font_data[i].font, font, sizeof(lv_font_t));
            i18n_font_data[0].font_p = font;
        }
        else
        {
            log_e("Failed to create %s", i18n_font_data[i].name);
            return false;
        }
    }
    return true;
}
#endif
void base_font_init(uint8_t *data, uint32_t size)
{
    lv_font_t *font;

    for (uint8_t i = 0; i < sizeof(base_font) / sizeof(base_font[0]); i++)
    {
        font = lv_tiny_ttf_create_data(data, size, base_font[i].line_height);
        if (font)
        {
            font->fallback = &i18n_null;
            log_i("Create %s", base_font[i].name);
            *(lv_font_t **)base_font[i].font = font;
        }
        else
            log_e("Failed to create %s", base_font[i].name);
    }
}

#if defined(CONFIG_IDF_TARGET) && (defined(FONT_ENABLE_BASE_i18n) || defined(FONT_ENABLE_i18n))
bool font_load_partition(uint8_t **buf, uint32_t *size)
{
    if (!size || !buf)
        return false;
    esp_partition_t *partition_font = NULL;
    partition_font = esp_partition_find_first(0x50, 0x12, "ttf");
    if (!partition_font)
    {
        log_e("Not find partition ttf");
        return false;
    }
    void *result = 0;
    esp_partition_mmap(partition_font, 0, partition_font->size, ESP_PARTITION_MMAP_DATA, (const void **)&result, &handle);
    if (!result)
    {
        log_e("Failed to mmap ttf partition");
        return false;
    }
    uint32_t par_size = partition_font->size;
    for (uint32_t i = partition_font->size - 1; i > 0; i--)
    {
        if (((uint8_t *)result)[i] != 0xFF)
            break;
        par_size--;
    }
    if (par_size != partition_font->size)
    {
        esp_partition_munmap(handle);
        esp_partition_mmap(partition_font, 0, par_size, ESP_PARTITION_MMAP_DATA, (const void *)&result, &handle);
        if (!result)
        {
            log_e("Failed to mmap ttf partition");
            handle = 0;
            return false;
        }
        log_i("TTF Size %ld KB, Partition Size %ld KB", par_size / 1024, partition_font->size / 1024);
    }
    *size = par_size;
    *buf = result;
    return true;
}
#endif

#ifdef FONT_ENABLE_FILE
bool font_load_file(char *path, uint8_t **buf, uint32_t *buf_size)
{
    FILE *f = fopen(path, "r");
    if (f)
    {
        if (fseek(f, 0, SEEK_END))
        {
            log_e("Failed to SEEK End");
            goto font_load_file_close;
        }
        uint32_t size = ftell(f);
        uint8_t *data = malloc(size);
        if (data)
        {
            if (fseek(f, 0, SEEK_SET))
            {
                log_e("Failed to SEEK Head");
                goto font_load_file_close;
            }
            if (!fread(data, size, 1, f))
            {
                log_e("Failed to Read Data");
                goto font_load_file_close;
            }
            *buf = data;
            *buf_size = size;
            return true;
        }
        else
            log_e("Failed to malloc buffer (%ld)\n", size);
    font_load_file_close:
        fclose(f);
    }
    else
        log_e("Failed to open file %s\n", path);
    return false;
}
#endif

void font_init(void)
{
    log_i("font init");
#if defined(CONFIG_IDF_TARGET) && defined(FONT_ENABLE_BASE_i18n) || defined(FONT_ENABLE_i18n)
    if (!handle)
    {
        font_load_partition(&i18n_font_buf, &i18n_font_buf_size);
    }
#endif

    if (base_font8 == &i18n_null)
    {
#if defined(FONT_ENABLE_BASE_i18n)
        base_font_init(i18n_font_buf, i18n_font_buf_size);
#else
        base_font_init(base_font_ttf, base_font_ttf_len);
#endif
    }

#if defined(FONT_ENABLE_i18n)
    uint8_t i18n_load_base = 0;
#if defined(FONT_ENABLE_FILE)
    if (!i18n_font_buf)
    {
        font_load_file("./ui/font/i18n.ttf", &i18n_font_buf, &i18n_font_buf_size);
    }
#endif
    if (i18n_font_buf)
    {
        if (!i18n_font_load(i18n_font_buf, i18n_font_buf_size))
            i18n_load_base = 1;
    }
    else
        i18n_load_base = 1;
    if (i18n_load_base)
    {
        lv_font_t *font;
        uint8_t index;
        for (uint8_t i = 0; i < sizeof(i18n_font_data) / sizeof(i18n_font_data[0]); i++)
        {
            if (*i18n_font_data[i].unload_font != &i18n_null)
            {
                index = ((uintptr_t)i18n_font_data[i].unload_font - (uintptr_t)&base_font8) / sizeof(uintptr_t);
                if (index >= sizeof(base_font) / sizeof(base_font[0]))
                {
                    log_e("Overflow %d", index);
                    return;
                }
                log_w("%s load %s", i18n_font_data[i].name, base_font[index].name);
                memcpy(&i18n_font_data[i].font, *(lv_font_t **)base_font[index].font, sizeof(lv_font_t));
            }
        }
    }
#endif
}

void font_deinit(void)
{
    log_i("font deinit");
#if defined(FONT_ENABLE_i18n)
#ifdef CONFIG_IDF_TARGET
    if (handle)
    {
        esp_partition_munmap(handle);
        handle = 0;
        i18n_font_buf = 0;
    }
#endif
    for (uint8_t i = 0; i < sizeof(i18n_font_data) / sizeof(i18n_font_data[0]); i++)
    {
        if (i18n_font_data[i].font_p)
        {
            lv_tiny_ttf_destroy(i18n_font_data[i].font_p);
            i18n_font_data[i].font_p = 0;
        }
        i18n_font_data[i].font.get_glyph_bitmap = __user_font_get_bitmap;
        i18n_font_data[i].font.get_glyph_dsc = __user_font_get_glyph_dsc;
    }
#endif
}

bool font_change_line_height(lv_font_t *font, uint16_t line_height)
{
    if (font->line_height == line_height)
        return true;
#if defined(FONT_ENABLE_i18n)
    if (!i18n_font_buf)
        return false;
    for (uint8_t i = 0; i < sizeof(i18n_font_data) / sizeof(i18n_font_data[0]); i++)
    {
        if (font == &i18n_font_data[i].font)
        {
            lv_font_t *font = lv_tiny_ttf_create_data(i18n_font_buf, i18n_font_buf_size, line_height);
            if (font)
            {
                if (i18n_font_data[i].font_p)
                    lv_tiny_ttf_destroy(i18n_font_data[i].font_p);
                log_i("%s change line height to %d", i18n_font_data[i].name, line_height);
                memcpy(&i18n_font_data[i].font, font, sizeof(lv_font_t));
                i18n_font_data[i].font_p = font;
                return true;
            }
            else
            {
                log_e("Failed to change line height %s", i18n_font_data[i].name);
                return false;
            }
            break;
        }
    }
    return false;
#else
    return true;
#endif
}
